/*
 *
 *  *    Copyright 2020-2021 Luter.me
 *  *
 *  *    Licensed under the Apache License, Version 2.0 (the "License");
 *  *    you may not use this file except in compliance with the License.
 *  *    You may obtain a copy of the License at
 *  *
 *  *      http://www.apache.org/licenses/LICENSE-2.0
 *  *
 *  *    Unless required by applicable law or agreed to in writing, software
 *  *    distributed under the License is distributed on an "AS IS" BASIS,
 *  *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  *    See the License for the specific language governing permissions and
 *  *    limitations under the License.
 *
 */

package com.luter.heimdall.boot.web.interceptor;

import com.luter.heimdall.core.annotation.*;
import com.luter.heimdall.core.config.ConfigManager;
import com.luter.heimdall.core.config.HeimdallProperties;
import com.luter.heimdall.core.fuction.AbcVoidFunction;
import com.luter.heimdall.core.manager.AuthorizationManager;
import com.luter.heimdall.core.token.SimpleToken;
import com.luter.heimdall.core.utils.PathUtil;
import org.slf4j.Logger;
import org.springframework.http.HttpMethod;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;

import static org.slf4j.LoggerFactory.getLogger;

/**
 * 认证授权拦截器
 *
 * @author luter
 */
public class HeimdallAuthorizeInterceptor implements HandlerInterceptor {
    /**
     * The constant log.
     */
    private static final transient Logger log = getLogger(HeimdallAuthorizeInterceptor.class);
    /**
     * The constant PATH_UTIL.
     */
    private static final PathUtil PATH_UTIL = new PathUtil();

    /**
     * 默认排除常见静态资源
     * <p>
     * "/static/**",
     * <p>
     * "/images/**",
     * <p>
     * "/css/**",
     * <p>
     * "/js/**",
     * <p>
     * "/favicon*"
     */
    public static final String[] DEFAULT_STATIC_RESOURCE = new String[]{
            "/static/**",
            "/images/**",
            "/css/**",
            "/js/**",
            "/favicon*",
            "/error",
    };
    /**
     * The Auth manager.
     */
    private AuthorizationManager authorizationManager;
    /**
     * 成功后处理
     */
    AbcVoidFunction<HttpServletRequest, HttpServletResponse, SimpleToken> success;
    /**
     * 失败后处理
     */
    AbcVoidFunction<HttpServletRequest, HttpServletResponse, Throwable> error;

    /**
     * 认证授权拦截器初始化
     *
     * @param authorizationManager 认证授权管理器
     */
    public HeimdallAuthorizeInterceptor(AuthorizationManager authorizationManager) {
        this.authorizationManager = authorizationManager;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        // 获取处理method
        if (!(handler instanceof HandlerMethod)) {
            return true;
        }
        final String method = request.getMethod();
        final String uri = request.getRequestURI();
        if (method.equalsIgnoreCase(HttpMethod.OPTIONS.name())) {
            //options 请求全部放行
            return true;
        }
        //看看拦住的方法上是不是有注解，有注解走注解，没注解走路由
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        final String anyAnnotationExist = getAnyAnnotationsName(handlerMethod);
        //没发现注解，走路由鉴权
        if (null == anyAnnotationExist) {
            final HeimdallProperties config = ConfigManager.getConfig();
            final boolean enabled = config.getAuthority().isEnabled();
            if (!enabled) {
                log.warn("[HeimdallAuthorizeInterceptor::preHandle]::  Attention: The authorization interceptor was disabled ");
                return true;
            }
            final List<String> includePatterns = config.getAuthority().getIncludes();
            final List<String> excludePatterns = config.getAuthority().getExcludes();
            //需要拦截还没被排除的
            final boolean matched = PATH_UTIL.isMatch(includePatterns, uri)
                    && !PATH_UTIL.isMatch(excludePatterns, uri);
            log.debug("[HeimdallAuthorizeInterceptor::preHandle]::method = [{}], uri = [{}], matched = [{}]",
                    method, uri, matched);
            if (matched) {
                log.info("[HeimdallAuthorizeInterceptor::preHandle]::Authorization rules were matched . resource = [{}:{}]", method, uri);
                try {
                    final SimpleToken currentToken = authorizationManager
                            .getAuthenticationManager().getCurrentToken(true);
                    authorizationManager.isAuthorized(currentToken.getDetails(), method, uri, true);
                    log.debug("[HeimdallAuthorizeInterceptor::preHandle]:: Access permitted. resource = [{}:{}], user = [{}]",
                            method, uri, currentToken.getDetails());
                    if (null != success) {
                        success.accept(request, response, currentToken);
                    }
                } catch (Throwable throwable) {
                    log.debug("[HeimdallAuthorizeInterceptor::preHandle]:: Remove all data from ThreadLocal " +
                            "of AuthSecurityUtil when authorization exception occurs");
                    log.warn("[HeimdallAuthorizeInterceptor::preHandle]:: Access Denied. resource = [{}:{}],error = {}", method, uri, throwable.getMessage());
                    if (null != error) {
                        error.accept(request, response, throwable);
                    } else {
                        throw throwable;
                    }
                }
            }

        } else {
            log.info("[HeimdallAuthorizeInterceptor::preHandle]::Annotation: [@{}] is detected with method:[{}], " +
                            "and the path based authorization function will be ignored." +
                            "Annotation=[{}] authorization will be activated.",
                    anyAnnotationExist, handlerMethod.getMethod(), anyAnnotationExist);
        }
        //最终都返回 true
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
        log.debug("[postHandle]::request [{}:{}]", request.getMethod(), request.getRequestURI());
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        log.debug("[afterCompletion]::request [{}:{}]", request.getMethod(), request.getRequestURI());
    }

    /**
     * 检查方法上是否存在注解
     *
     * @param method 方法
     * @return 注解的SimpleName
     */
    private static String getAnyAnnotationsName(HandlerMethod method) {
        if (method.hasMethodAnnotation(RequiresUser.class)) {
            return RequiresRole.class.getSimpleName();
        } else if (method.hasMethodAnnotation(RequiresRole.class)) {
            return RequiresRole.class.getSimpleName();
        } else if (method.hasMethodAnnotation(RequiresRoles.class)) {
            return RequiresRoles.class.getSimpleName();
        } else if (method.hasMethodAnnotation(RequiresPermission.class)) {
            return RequiresPermission.class.getSimpleName();
        } else if (method.hasMethodAnnotation(RequiresPermissions.class)) {
            return RequiresPermissions.class.getSimpleName();
        }
        return null;
    }

    /**
     * Is any annotations exist boolean.
     *
     * @param method the method
     * @return the boolean
     */
    private static boolean isAnyAnnotationsExist(HandlerMethod method) {
        return method.hasMethodAnnotation(RequiresUser.class) ||
                method.hasMethodAnnotation(RequiresRole.class) ||
                method.hasMethodAnnotation(RequiresRoles.class) ||
                method.hasMethodAnnotation(RequiresPermission.class) ||
                method.hasMethodAnnotation(RequiresPermissions.class);
    }

    /**
     * Gets authorization manager.
     *
     * @return the authorization manager
     */
    public AuthorizationManager getAuthorizationManager() {
        return authorizationManager;
    }

    /**
     * Sets authorization manager.
     *
     * @param authorizationManager the authorization manager
     * @return the authorization manager
     */
    public HeimdallAuthorizeInterceptor setAuthorizationManager(AuthorizationManager authorizationManager) {
        this.authorizationManager = authorizationManager;
        return this;
    }

    /**
     * On success auth way authorize interceptor.
     *
     * @param success the success
     * @return the auth way authorize interceptor
     */
    public HeimdallAuthorizeInterceptor onSuccess(AbcVoidFunction<HttpServletRequest, HttpServletResponse, SimpleToken> success) {
        this.success = success;
        return this;
    }

    /**
     * On error auth way authorize interceptor.
     *
     * @param error the error
     * @return the auth way authorize interceptor
     */
    public HeimdallAuthorizeInterceptor onError(AbcVoidFunction<HttpServletRequest, HttpServletResponse, Throwable> error) {
        this.error = error;
        return this;
    }
}
